use crate::token::{TextSpan, Token, TokenKind};
// use crate::LEVEL_MARK;

pub struct Lexer {
  input: String,
  current_pos: usize,
}

impl Lexer {
  pub fn new(input: &str) -> Self {
    Self {
      input: input.to_string(),
      current_pos: 0,
    }
  }

  // pub fn tokenizes(&mut self) -> Vec<Token> {
  //   let mut tokens = Vec::new();
  //   while let Some(token) = self.next_token() {
  //     if token.kind == TokenKind::Whitespace {
  //       continue;
  //     }
  //     tokens.push(token);
  //   }
  //
  //   // if LEVEL_MARK {
  //   //   for token in &tokens {
  //   //     println!("{token:?}");
  //   //   }
  //   // }
  //
  //   tokens
  // }
  pub fn tokenizes(&mut self) -> Vec<Token> {
    let mut tokens = Vec::new();
    while let Some(token) = self.next_token() {
      tokens.push(token);
    }
    // tokens
    //   .iter()
    //   .filter(|token| token.kind != TokenKind::Whitespace)
    //   .map(|token| token.clone())
    //   .collect()
    tokens
      .iter()
      .filter(|token| token.kind != TokenKind::Whitespace)
      .cloned()
      .collect()
  }

  fn next_token(&mut self) -> Option<Token> {
    if self.current_pos == self.input.len() {
      self.current_pos += 1;
      let eof_char = '\0'.to_string();
      return Some(Token::new(TokenKind::Eof, TextSpan::new(0, 0, eof_char)));
    }

    self.current_char().map(|c| {
      let start = self.current_pos;
      let kind = if Self::is_number_start(&c) {
        let number = self.consume_number().unwrap();
        TokenKind::Number(number)
      } else if Self::is_whitespace(&c) {
        self.consume();
        // println!("\\r or \\n or \\t or Whitespace: {:#?}", c);
        TokenKind::Whitespace
      } else if Self::is_identifier_start(&c) {
        let identifier = self.consume_identifier();
        match identifier.as_str() {
          "let" => TokenKind::Let,
          "true" => TokenKind::True,
          "false" => TokenKind::False,
          _ => TokenKind::Identifier,
        }
      } else {
        self.consume_punctuation()
      };

      let end = self.current_pos;
      let literal = self.input[start..end].to_string();
      let span = TextSpan::new(start, end, literal);
      Token::new(kind, span)
    })
  }

  fn is_number_start(c: &char) -> bool {
    // c.is_digit(10)
    c.is_numeric()
  }

  fn is_identifier_start(c: &char) -> bool {
    // c.is_alphabetic() 不包含数字的 abc
    // c.is_alphanumeric() 包含数组 a123
    c.is_alphanumeric() || c == &'_'
  }

  fn is_whitespace(c: &char) -> bool {
    // c.is_whitespace()
    c.is_ascii_whitespace()
  }

  fn current_char(&self) -> Option<char> {
    self.input.chars().nth(self.current_pos)
  }

  fn consume(&mut self) -> Option<char> {
    if self.current_pos >= self.input.len() {
      return None;
    }
    let c = self.current_char();
    self.current_pos += 1;

    c
  }

  fn consume_identifier(&mut self) -> String {
    let mut identifier = String::new();
    while let Some(c) = self.current_char() {
      if Self::is_identifier_start(&c) {
        self.consume().unwrap();
        identifier.push(c);
      } else {
        break;
      }
    }
    identifier
  }

  fn consume_number(&mut self) -> Option<f64> {
    let mut number = String::new();
    let mut have_dot = false; // 不允许 “1.”  “2.”  “3.”  这样的写法

    while let Some(c) = self.current_char() {
      // if c.is_digit(10) {
      if c.is_numeric() {
        self.consume().unwrap();
        number.push(c);
      } else if c == '.' {
        self.consume().unwrap();
        if have_dot {
          return None;
        }
        have_dot = true;
        number.push('.');
      } else {
        break;
      }
    }

    // if have_dot && number.chars().last().unwrap() == '.' {
    if have_dot && number.ends_with('.') {
      // 不允许 “1.”  “2.”  “3.”  这样的写法, 必须 “2.0” 写法
      // return None;
      panic!("Incorrect decimal point writing. Example: 1.0, Write it correctly.");
    }

    Some(number.parse::<f64>().unwrap())
  }

  fn consume_punctuation(&mut self) -> TokenKind {
    let c = self.consume().unwrap();
    match c {
      '=' => TokenKind::Assign,
      '+' => TokenKind::Plus,
      '-' => TokenKind::Minus,
      '*' => TokenKind::Asterisk,
      '/' => TokenKind::Slash,
      '(' => TokenKind::LeftParen,
      ')' => TokenKind::RightParen,
      ';' => TokenKind::SemiColon,
      '!' => TokenKind::Bang,
      _ => TokenKind::Bad,
    }
  }
}
